// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/gpu/browser_gpu_channel_host_factory.h"
#include "content/browser/gpu/gpu_process_host_ui_shim.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/content_browser_test.h"
#include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "ui/gl/gl_switches.h"

namespace {

scoped_refptr<ui::ContextProviderCommandBuffer> CreateContext(
    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host)
{
    // This is for an offscreen context, so the default framebuffer doesn't need
    // any alpha, depth, stencil, antialiasing.
    gpu::gles2::ContextCreationAttribHelper attributes;
    attributes.alpha_size = -1;
    attributes.depth_size = 0;
    attributes.stencil_size = 0;
    attributes.samples = 0;
    attributes.sample_buffers = 0;
    attributes.bind_generates_resource = false;
    constexpr bool automatic_flushes = false;
    constexpr bool support_locking = false;
    return make_scoped_refptr(new ui::ContextProviderCommandBuffer(
        std::move(gpu_channel_host), gpu::GPU_STREAM_DEFAULT,
        gpu::GpuStreamPriority::NORMAL, gpu::kNullSurfaceHandle, GURL(),
        automatic_flushes, support_locking, gpu::SharedMemoryLimits(), attributes,
        nullptr, ui::command_buffer_metrics::OFFSCREEN_CONTEXT_FOR_TESTING));
}

void OnEstablishedGpuChannel(
    const base::Closure& quit_closure,
    scoped_refptr<gpu::GpuChannelHost>* retvalue,
    scoped_refptr<gpu::GpuChannelHost> established_host)
{
    if (retvalue)
        *retvalue = std::move(established_host);
    quit_closure.Run();
}

class EstablishGpuChannelHelper {
public:
    EstablishGpuChannelHelper() { }
    ~EstablishGpuChannelHelper() { }

    scoped_refptr<gpu::GpuChannelHost> EstablishGpuChannelSyncRunLoop()
    {
        if (!content::BrowserGpuChannelHostFactory::instance())
            content::BrowserGpuChannelHostFactory::Initialize(true);

        content::BrowserGpuChannelHostFactory* factory = content::BrowserGpuChannelHostFactory::instance();
        CHECK(factory);
        base::RunLoop run_loop;
        factory->EstablishGpuChannel(base::Bind(
            &OnEstablishedGpuChannel, run_loop.QuitClosure(), &gpu_channel_host_));
        run_loop.Run();
        return std::move(gpu_channel_host_);
    }

private:
    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
    DISALLOW_COPY_AND_ASSIGN(EstablishGpuChannelHelper);
};

class ContextTestBase : public content::ContentBrowserTest {
public:
    void SetUpOnMainThread() override
    {
        // This may leave the provider_ null in some cases, so tests need to early
        // out.
        if (!content::BrowserGpuChannelHostFactory::CanUseForTesting())
            return;

        EstablishGpuChannelHelper helper;
        scoped_refptr<gpu::GpuChannelHost> gpu_channel_host = helper.EstablishGpuChannelSyncRunLoop();
        CHECK(gpu_channel_host);

        provider_ = CreateContext(std::move(gpu_channel_host));
        bool bound = provider_->BindToCurrentThread();
        CHECK(bound);
        gl_ = provider_->ContextGL();
        context_support_ = provider_->ContextSupport();

        ContentBrowserTest::SetUpOnMainThread();
    }

    void TearDownOnMainThread() override
    {
        // Must delete the context first.
        provider_ = nullptr;
        ContentBrowserTest::TearDownOnMainThread();
    }

protected:
    gpu::gles2::GLES2Interface* gl_ = nullptr;
    gpu::ContextSupport* context_support_ = nullptr;

private:
    scoped_refptr<ui::ContextProviderCommandBuffer> provider_;
};

} // namespace

// Include the shared tests.
#define CONTEXT_TEST_F IN_PROC_BROWSER_TEST_F
#include "gpu/ipc/client/gpu_context_tests.h"

namespace content {

class BrowserGpuChannelHostFactoryTest : public ContentBrowserTest {
public:
    void SetUpOnMainThread() override
    {
        if (!BrowserGpuChannelHostFactory::CanUseForTesting())
            return;

        // Start all tests without a gpu channel so that the tests exercise a
        // consistent codepath.
        if (!BrowserGpuChannelHostFactory::instance())
            BrowserGpuChannelHostFactory::Initialize(false);
        CHECK(GetFactory());

        ContentBrowserTest::SetUpOnMainThread();
    }

    void SetUpCommandLine(base::CommandLine* command_line) override
    {
        // Start all tests without a gpu channel so that the tests exercise a
        // consistent codepath.
        command_line->AppendSwitch(switches::kDisableGpuEarlyInit);
    }

    void OnContextLost(const base::Closure callback, int* counter)
    {
        (*counter)++;
        callback.Run();
    }

    void Signal(bool* event,
        scoped_refptr<gpu::GpuChannelHost> gpu_channel_host)
    {
        CHECK_EQ(*event, false);
        *event = true;
        gpu_channel_host_ = std::move(gpu_channel_host);
    }

protected:
    BrowserGpuChannelHostFactory* GetFactory()
    {
        return BrowserGpuChannelHostFactory::instance();
    }

    bool IsChannelEstablished()
    {
        return gpu_channel_host_ && !gpu_channel_host_->IsLost();
    }

    void EstablishAndWait()
    {
        EstablishGpuChannelHelper helper;
        gpu_channel_host_ = helper.EstablishGpuChannelSyncRunLoop();
    }

    gpu::GpuChannelHost* GetGpuChannel() { return gpu_channel_host_.get(); }

    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
};

// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_Basic Basic
#else
#define MAYBE_Basic DISABLED_Basic
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest, MAYBE_Basic)
{
    DCHECK(!IsChannelEstablished());
    EstablishAndWait();
    EXPECT_TRUE(GetGpuChannel() != NULL);
}

// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_EstablishAndTerminate EstablishAndTerminate
#else
#define MAYBE_EstablishAndTerminate DISABLED_EstablishAndTerminate
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
    MAYBE_EstablishAndTerminate)
{
    DCHECK(!IsChannelEstablished());
    base::RunLoop run_loop;
    GetFactory()->EstablishGpuChannel(
        base::Bind(&OnEstablishedGpuChannel, run_loop.QuitClosure(), nullptr));
    GetFactory()->Terminate();

    // The callback should still trigger.
    run_loop.Run();
}

#if !defined(OS_ANDROID)
// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_AlreadyEstablished AlreadyEstablished
#else
#define MAYBE_AlreadyEstablished DISABLED_AlreadyEstablished
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
    MAYBE_AlreadyEstablished)
{
    DCHECK(!IsChannelEstablished());
    scoped_refptr<gpu::GpuChannelHost> gpu_channel = GetFactory()->EstablishGpuChannelSync();

    // Expect established callback immediately.
    bool event = false;
    GetFactory()->EstablishGpuChannel(
        base::Bind(&BrowserGpuChannelHostFactoryTest::Signal,
            base::Unretained(this), &event));
    EXPECT_TRUE(event);
    EXPECT_EQ(gpu_channel.get(), GetGpuChannel());
}
#endif

// Test fails on Windows because GPU Channel set-up fails.
#if !defined(OS_WIN)
#define MAYBE_GrContextKeepsGpuChannelAlive GrContextKeepsGpuChannelAlive
#else
#define MAYBE_GrContextKeepsGpuChannelAlive \
    DISABLED_GrContextKeepsGpuChannelAlive
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
    MAYBE_GrContextKeepsGpuChannelAlive)
{
    // Test for crbug.com/551143
    // This test verifies that holding a reference to the GrContext created by
    // a ui::ContextProviderCommandBuffer will keep the gpu channel alive after
    // the
    // provider has been destroyed. Without this behavior, user code would have
    // to be careful to destroy objects in the right order to avoid using freed
    // memory as a function pointer in the GrContext's GrGLInterface instance.
    DCHECK(!IsChannelEstablished());
    EstablishAndWait();

    // Step 2: verify that holding onto the provider's GrContext will
    // retain the host after provider is destroyed.
    scoped_refptr<ui::ContextProviderCommandBuffer> provider = CreateContext(GetGpuChannel());
    EXPECT_TRUE(provider->BindToCurrentThread());

    sk_sp<GrContext> gr_context = sk_ref_sp(provider->GrContext());

    SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
        gr_context.get(), SkBudgeted::kNo, info);
    EXPECT_TRUE(surface);

    // Destroy the GL context after we made a surface.
    provider = nullptr;

    // New surfaces will fail to create now.
    sk_sp<SkSurface> surface2 = SkSurface::MakeRenderTarget(gr_context.get(), SkBudgeted::kNo, info);
    EXPECT_FALSE(surface2);

    // Drop our reference to the gr_context also.
    gr_context = nullptr;

    // After the context provider is destroyed, the surface no longer has access
    // to the GrContext, even though it's alive. Use the canvas after the provider
    // and GrContext have been locally unref'ed. This should work fine as the
    // GrContext has been abandoned when the GL context provider was destroyed
    // above.
    SkPaint greenFillPaint;
    greenFillPaint.setColor(SK_ColorGREEN);
    greenFillPaint.setStyle(SkPaint::kFill_Style);
    // Passes by not crashing
    surface->getCanvas()->drawRect(SkRect::MakeWH(100, 100), greenFillPaint);
}

// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_CrashAndRecover CrashAndRecover
#else
#define MAYBE_CrashAndRecover DISABLED_CrashAndRecover
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
    MAYBE_CrashAndRecover)
{
    DCHECK(!IsChannelEstablished());
    EstablishAndWait();
    scoped_refptr<gpu::GpuChannelHost> host = GetGpuChannel();

    scoped_refptr<ui::ContextProviderCommandBuffer> provider = CreateContext(GetGpuChannel());
    base::RunLoop run_loop;
    int counter = 0;
    provider->SetLostContextCallback(
        base::Bind(&BrowserGpuChannelHostFactoryTest::OnContextLost,
            base::Unretained(this), run_loop.QuitClosure(), &counter));
    EXPECT_TRUE(provider->BindToCurrentThread());
    GpuProcessHostUIShim* shim = GpuProcessHostUIShim::FromID(GetFactory()->GpuProcessHostId());
    EXPECT_TRUE(shim != NULL);
    shim->SimulateCrash();
    run_loop.Run();

    EXPECT_EQ(1, counter);
    EXPECT_FALSE(IsChannelEstablished());
    EstablishAndWait();
    EXPECT_TRUE(IsChannelEstablished());
}

} // namespace content
